﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using wpf4gp.Helpers;
using common4gp;
using System.ComponentModel;
using System.Reflection;
using System.Windows.Input;
using System.Windows;
using System.Windows.Threading;

namespace wpf4gp.ViewModels
{

    /// <summary>
    /// View Model for <see cref="wpf4gp.Views.ExceptionWindow"/>
    /// </summary>
    public class ExceptionDetailsViewModel : BaseWindowViewModel
    {

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="ex"><see cref="System.Exception"/> to show</param>
        public ExceptionDetailsViewModel(Exception ex)
        {
            IsLoading = false;
            Exception = ex;
        }



        #region Exception

        private Exception _exception;

        /// <summary>
        /// <see cref="System.Exception"/> shown
        /// </summary>
        public Exception Exception
        {
            get
            {
                return _exception;
            }
            private set
            {
                if (_exception != value)
                {
                    _exception = value;
                    RaisePropertyChanged(() => Exception);
                    LoadDetails(value);
                }
            }
        }


        #endregion


        #region Details of the Exception


        private ObservableCollection<ExceptionNode> _details;

        /// <summary>
        /// Exception details
        /// </summary>
        public ObservableCollection<ExceptionNode> Details
        {
            get
            {
                return _details;
            }
            set
            {
                if (_details != value)
                {
                    _details = value;
                    RaisePropertyChanged(() => Details);
                }
            }
        }

        /// <summary>
        /// Load exception details
        /// </summary>
        /// <param name="ex">Exception</param>
        private void LoadDetails(Exception ex)
        {
            IsLoading = true;
            try
            {
                BackgroundWorker exceptionWorker = new BackgroundWorker();
                exceptionWorker.DoWork += exceptionWorker_DoWork;
                exceptionWorker.RunWorkerAsync(ex);
            }
            finally
            {
                Details = null;
                IsLoading = false;
            }

        }

        /// <summary>
        /// Events for load details async
        /// </summary>
        /// <param name="sender">Sender</param>
        /// <param name="e">Parameters</param>
        private void exceptionWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            Exception ex = e.Argument as Exception;
            ExceptionNode mainNode = new ExceptionNode("Exception", ex.Message);
            if (ex.IsNotNull())
            {
                loadNodes(ex, mainNode);
            }
            ObservableCollection<ExceptionNode> details = new ObservableCollection<ExceptionNode>();
            details.Add(mainNode);
            Details = details;
        }

        /// <summary>
        /// Load exception nodes to a parent
        /// </summary>
        /// <param name="ex">Exception to load data from</param>
        /// <param name="parent">Parent node</param>
        private void loadNodes(Exception ex, ExceptionNode parent)
        {
            Contract.Requires<ArgumentNullException>(ex.IsNotNull());
            Contract.Requires<ArgumentNullException>(parent.IsNotNull());
            parent.Children.Add(new ExceptionNode("Type", ex.GetType().Name, parent));
            foreach (PropertyInfo pInfo in ex.GetType().GetProperties())
            {
                var value = pInfo.GetValue(ex, null);
                if (value.IsNotNull())
                {
                    if (string.Equals(pInfo.Name, "InnerException", StringComparison.InvariantCultureIgnoreCase))
                    {
                        ExceptionNode innerParent = new ExceptionNode(pInfo.Name, string.Empty, parent);
                        loadNodes(ex.InnerException, innerParent);
                        parent.Children.Add(innerParent);
                    }
                    else if (string.Equals(pInfo.Name, "Data", StringComparison.InvariantCultureIgnoreCase))
                    {
                        if (!ex.Data.IsNullOrEmpty())
                        {
                            ExceptionNode dataParent = new ExceptionNode(pInfo.Name, string.Empty, parent);
                            foreach (var key in ex.Data.Keys)
                            {
                                var data = ex.Data[key];
                                if (data.IsNotNull())
                                {
                                    ExceptionNode childNode = new ExceptionNode(key.ToString(), data.ToString(), dataParent);
                                    dataParent.Children.Add(childNode);
                                }
                            }
                            parent.Children.Add(dataParent);
                        }
                    }
                    else
                    {
                        parent.Children.Add(new ExceptionNode(pInfo.Name, value.ToString(), parent));
                    }
                }
            }
        }


        #endregion


        #region Selected node

        private ExceptionNode _selectedNode = null;

        /// <summary>
        /// Selected node
        /// </summary>
        public ExceptionNode SelectedNode
        {
            get
            {
                return _selectedNode;
            }
            set
            {
                if (_selectedNode != value)
                {
                    _selectedNode = value;
                    RaisePropertyChanged(() => SelectedNode);
                }
            }
        }

        #endregion

        #region Copy to clipboard

        bool CanCopyToClipboard()
        {
            return Exception.IsNotNull() &&
                   IsLoading == false;
        }

        public ICommand CopyToClipboardCommand
        {
            get
            {
                return new DelegateCommand(CopyToClipboard, CanCopyToClipboard);
            }
        }

        /// <summary>
        /// Copy the exception to the clipboard
        /// </summary>
        private void CopyToClipboard()
        {
            Dispatcher.CurrentDispatcher.BeginInvoke((Action)(() =>
                {
                    Clipboard.SetText(Exception.DetailedMessage());
                }));
        }


        #endregion


        #region Exit


        /// <summary>
        /// Close the window
        /// </summary>
        public ICommand CloseCommand
        {
            get
            {
                return new DelegateCommand(() => DialogResult = true, () => !IsLoading);
            }
        }

        #endregion


    }


    public class ExceptionNode
    {

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name">Name of the node</param>
        /// <param name="description">Value of the node</param>
        /// <param name="parent">Parent node (if any)</param>
        public ExceptionNode(string name, string description, ExceptionNode parent = null)
        {
            Contract.Requires<ArgumentNullException>(name.IsNotNull());
            Contract.Requires<ArgumentNullException>(description.IsNotNull());
            Name = name;
            Description = description;
            Parent = parent;
            Children = new ObservableCollection<ExceptionNode>();
        }

        /// <summary>
        /// Name of the node
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Description of the node
        /// </summary>
        public string Description { get; private set; }


        /// <summary>
        /// Parent
        /// </summary>
        public ExceptionNode Parent { get; private set; }

        /// <summary>
        /// Children
        /// </summary>
        public ObservableCollection<ExceptionNode> Children { get; private set; }

        public override string ToString()
        {
            return Description;
        }

    }
}
